package cc.jq1024.infrastructure.persistent.repository;

import cc.jq1024.domain.award.model.aggregate.GiveOutPrizesAggregate;
import cc.jq1024.domain.award.model.aggregate.UserAwardRecordAggregate;
import cc.jq1024.domain.award.model.entity.TaskEntity;
import cc.jq1024.domain.award.model.entity.UserAwardRecordEntity;
import cc.jq1024.domain.award.model.entity.UserCreditAwardEntity;
import cc.jq1024.domain.award.model.valobj.AccountStatusVO;
import cc.jq1024.domain.award.repository.IAwardRepository;
import cc.jq1024.infrastructure.event.EventPublisher;
import cc.jq1024.infrastructure.persistent.dao.*;
import cc.jq1024.infrastructure.persistent.po.TaskPO;
import cc.jq1024.infrastructure.persistent.po.UserAwardRecordPO;
import cc.jq1024.infrastructure.persistent.po.UserCreditAccountPO;
import cc.jq1024.infrastructure.persistent.po.UserRaffleOrderPO;
import cc.jq1024.infrastructure.persistent.redis.IRedisService;
import cc.jq1024.router.strategy.IDBRouterStrategy;
import cc.jq1024.types.common.Constants;
import cc.jq1024.types.enums.ResponseCode;
import cc.jq1024.types.exception.AppException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

/**
 * @Author: jia-qiang ljq1024.cc
 * @desc: 奖品仓储服务 - 实现类
 * @Date: 2024-06-20-20:23
 */
@Slf4j
@Repository
public class AwardRepository implements IAwardRepository {

    @Resource
    private ITaskDao taskDao;
    @Resource
    private IAwardDao awardDao;
    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IUserAwardRecordDao userAwardRecordDao;
    @Resource
    private IUserRaffleOrderDao userRaffleOrderDao;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private EventPublisher eventPublisher;
    @Resource
    private IRedisService redisService;

    /**
     * 保存用户奖品流水
     * 存储聚合对象 - 一个事务下，用户的中奖记录
     * @param userAwardRecordAggregate 用户中奖聚合对象
     */
    @Override
    public void saveUserAwardRecord(UserAwardRecordAggregate userAwardRecordAggregate) {
        // 1. 获取基本数据
        TaskEntity taskEntity = userAwardRecordAggregate.getTaskEntity();
        UserAwardRecordEntity userAwardRecordEntity = userAwardRecordAggregate.getUserAwardRecordEntity();
        String userId = userAwardRecordEntity.getUserId();
        Long activityId = userAwardRecordEntity.getActivityId();
        Long awardId = userAwardRecordEntity.getAwardId();

        // 2. 类型转换
        UserAwardRecordPO userAwardRecord = UserAwardRecordPO.builder()
                .userId(userAwardRecordEntity.getUserId())
                .activityId(userAwardRecordEntity.getActivityId())
                .strategyId(userAwardRecordEntity.getStrategyId())
                .orderId(userAwardRecordEntity.getOrderId())
                .awardId(userAwardRecordEntity.getAwardId())
                .awardTitle(userAwardRecordEntity.getAwardTitle())
                .awardTime(userAwardRecordEntity.getAwardTime())
                .awardState(userAwardRecordEntity.getAwardState().getCode())
                .build();
        TaskPO task = TaskPO.builder()
                .userId(taskEntity.getUserId())
                .topic(taskEntity.getTopic())
                .messageId(taskEntity.getMessageId())
                .message(JSON.toJSONString(taskEntity.getMessage()))
                .state(taskEntity.getState().getCode())
                .build();
        UserRaffleOrderPO userRaffleOrderReq = new UserRaffleOrderPO();
        userRaffleOrderReq.setUserId(userAwardRecordEntity.getUserId());
        userRaffleOrderReq.setOrderId(userAwardRecordEntity.getOrderId());

        // 3. 插入用户中奖记录和task任务
        try {
            dbRouter.doRouter(userId); // 分库
            transactionTemplate.execute(status -> {
                try {
                    // 写入记录
                    userAwardRecordDao.insertOne(userAwardRecord);
                    // 写入任务
                    taskDao.insertOne(task);
                    // 更新抽奖单
                    int count = userRaffleOrderDao.updateUserRaffleOrderStateUsed(userRaffleOrderReq);
                    if (1 != count) {
                        status.setRollbackOnly();
                        log.error("写入中奖记录，用户抽奖单已使用过，不可重复抽奖 userId: {} activityId: {} awardId: {}", userId, activityId, awardId);
                        throw new AppException(ResponseCode.ACTIVITY_ORDER_ERROR.getCode(), ResponseCode.ACTIVITY_ORDER_ERROR.getInfo());
                    }
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入中奖记录，唯一索引冲突 userId: {} activityId: {} awardId: {}", userId, activityId, awardId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
        }

        try {
            // 4. 发送消息【在事务外执行，如果失败还有任务补偿】
            eventPublisher.publish(task.getTopic(), task.getMessage());
            // 5. 更新数据库记录，task 任务表
            taskDao.updateTaskSendMessageCompleted(task);
        } catch (Exception e) {
            log.error("写入中奖记录，发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
            taskDao.updateTaskSendMessageFail(task);
        }

    }


    /**
     * 查询奖品配置
     * @param awardId 奖品ID
     * @return 奖品配置【0.01-1， 1-100】等
     */
    @Override
    public String queryAwardConfig(Long awardId) {
        // 优先查询缓存
        String cacheKey = Constants.RedisKey.AWARD_CONFIG_KEY + awardId;
        String configValue = redisService.getValue(cacheKey, String.class);
        if (StringUtils.hasLength(configValue)) {
            // 缓存命中
            return configValue;
        }
        // 缓存未命中
        configValue = awardDao.queryAwardConfigByAwardId(awardId);
        redisService.setValue(configValue, String.class);
        return configValue;
    }

    /**
     * 存储发奖对象 - 积分发奖
     * @param giveOutPrizesAggregate 发放奖品聚合对象
     */
    @Override
    public void saveGiveOutPrizesAggregate(GiveOutPrizesAggregate giveOutPrizesAggregate) {
        // 获取基本数据
        String userId = giveOutPrizesAggregate.getUserId();
        UserCreditAwardEntity userCreditAwardEntity = giveOutPrizesAggregate.getUserCreditAwardEntity();
        UserAwardRecordEntity userAwardRecordEntity = giveOutPrizesAggregate.getUserAwardRecordEntity();

        // 类型转换 -> 更新发奖记录
        UserAwardRecordPO userAwardRecordReq = new UserAwardRecordPO();
        userAwardRecordReq.setUserId(userId);
        userAwardRecordReq.setOrderId(userAwardRecordEntity.getOrderId());
        userAwardRecordReq.setAwardState(userAwardRecordEntity.getAwardState().getCode());
        // 类型转换 -》 更新用户积分 「首次则插入数据」
        UserCreditAccountPO userCreditAccountReq = new UserCreditAccountPO();
        userCreditAccountReq.setUserId(userCreditAwardEntity.getUserId());
        userCreditAccountReq.setTotalAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAvailableAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode());

        try {
            // 设置分片键
            dbRouter.doRouter(giveOutPrizesAggregate.getUserId());
            transactionTemplate.execute(status -> {
                try {
                    // 更新积分 || 创建积分账户
                    int updateAccountCount = userCreditAccountDao.updateAddAmount(userCreditAccountReq);
                    if (0 == updateAccountCount) {
                        userCreditAccountDao.insertOne(userCreditAccountReq);
                    }
                    // 更新奖品记录
                    int updateAwardCount = userAwardRecordDao.updateAwardRecordCompletedState(userAwardRecordReq);
                    if (0 == updateAwardCount) {
                        log.warn("更新中奖记录，重复更新拦截 userId:{} giveOutPrizesAggregate:{}", userId, JSON.toJSONString(giveOutPrizesAggregate));
                        status.setRollbackOnly();
                    }
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("更新中奖记录，唯一索引冲突 userId: {} ", userId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
        }
    }

    /**
     * 查询奖品Key
     * @param awardId 奖品ID
     * @return 奖品Key
     */
    @Override
    public String queryAwardKey(Long awardId) {
        // 优先查询缓存
        String cacheKey = Constants.RedisKey.AWARD_KEY + awardId;
        String awardKey = redisService.getValue(cacheKey, String.class);
        if (StringUtils.hasLength(awardKey)) {
            // 缓存命中
            return awardKey;
        }
        // 缓存未命中
        awardKey = awardDao.queryAwardKeyByAwardId(awardId);
        redisService.setValue(awardKey, String.class);
        return awardKey;
    }
}
